// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable CompareOfFloatsByEqualityOperator
// ReSharper disable InconsistentNaming

/* Fill.cs --
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.Serialization;

#endregion

#nullable enable

namespace AM.Drawing.Charting;

/// <summary>
/// A class that encapsulates color-fill properties for an object.  The <see cref="Fill"/> class
/// is used in <see cref="PaneBase.Rect"/>, <see cref="Chart.Rect"/>, <see cref="Legend"/>,
/// <see cref="Bar"/>, and <see cref="Line"/> objects.
/// </summary>
[Serializable]
public class Fill
    : ISerializable, ICloneable
{
    #region Fields

    /// <summary>
    /// Private field that stores the fill color.  Use the public
    /// property <see cref="Color"/> to access this value.  This property is
    /// only applicable if the <see cref="Type"/> is not <see cref="FillType.None"/>.
    /// </summary>
    private Color _color;

    /// <summary>
    /// Private field that stores the secondary color for gradientByValue fills.  Use the public
    /// property <see cref="SecondaryValueGradientColor"/> to access this value.  This property is
    /// only applicable if the <see cref="Type"/> is <see cref="FillType.GradientByX"/>,
    /// <see cref="FillType.GradientByY"/>, or <see cref="FillType.GradientByZ"/>.
    /// </summary>
    private Color _secondaryValueGradientColor;

    /// <summary>
    /// Private field that stores the custom fill brush.  Use the public
    /// property <see cref="Brush"/> to access this value.  This property is
    /// only applicable if the
    /// <see cref="Type"/> property is set to <see cref="FillType.Brush"/>.
    /// </summary>
    protected Brush? _brush;

    /// <summary>
    /// Private field that determines the type of color fill.  Use the public
    /// property <see cref="Type"/> to access this value.  The fill color
    /// is determined by the property <see cref="Color"/> or
    /// <see cref="Brush"/>.
    /// </summary>
    private FillType _type;

    /// <summary>
    /// Private field that determines if the brush will be scaled to the bounding box
    /// of the filled object.  If this value is false, then the brush will only be aligned
    /// with the filled object based on the <see cref="AlignH"/> and <see cref="AlignV"/>
    /// properties.
    /// </summary>
    private bool _isScaled;

    /// <summary>
    /// Private field that determines how the brush will be aligned with the filled object
    /// in the horizontal direction.  This value is a <see cref="Charting.AlignH"/> enumeration.
    /// This field only applies if <see cref="IsScaled"/> is false.
    /// properties.
    /// </summary>
    /// <seealso cref="AlignH"/>
    /// <seealso cref="AlignV"/>
    private AlignH _alignH;

    /// <summary>
    /// Private field that determines how the brush will be aligned with the filled object
    /// in the vertical direction.  This value is a <see cref="Charting.AlignV"/> enumeration.
    /// This field only applies if <see cref="IsScaled"/> is false.
    /// properties.
    /// </summary>
    /// <seealso cref="AlignH"/>
    /// <seealso cref="AlignV"/>
    private AlignV _alignV;

    private double _rangeMin;
    private double _rangeMax;
    private double _rangeDefault;
    private Bitmap _gradientBM;

    /// <summary>
    /// Private field that saves the image passed to the constructor.
    /// This is used strictly for serialization.
    /// </summary>
    private Image _image;

    /// <summary>
    /// Private field that saves the image wrapmode passed to the constructor.
    /// This is used strictly for serialization.
    /// </summary>
    private WrapMode _wrapMode;

    /// <summary>
    /// Private field that saves the list of colors used to create the
    /// <see cref="LinearGradientBrush"/> in the constructor.  This is used strictly
    /// for serialization.
    /// </summary>
    private Color[]? _colorList;

    /// <summary>
    /// Private field that saves the list of positions used to create the
    /// <see cref="LinearGradientBrush"/> in the constructor.  This is used strictly
    /// for serialization.
    /// </summary>
    private float[] _positionList;

    /// <summary>
    /// Private field the saves the angle of the fill.  This is used strictly for serialization.
    /// </summary>
    private float _angle;

    #endregion

    #region Defaults

    /// <summary>
    /// A simple struct that defines the
    /// default property values for the <see cref="Fill"/> class.
    /// </summary>
    public struct Default
    {
        // Default Fill properties
        /// <summary>
        /// The default scaling mode for <see cref="Brush"/> fills.
        /// This is the default value for the <see cref="Fill.IsScaled"/> property.
        /// </summary>
        public static bool IsScaled = true;

        /// <summary>
        /// The default horizontal alignment for <see cref="Brush"/> fills.
        /// This is the default value for the <see cref="Fill.AlignH"/> property.
        /// </summary>
        public static AlignH AlignH = AlignH.Center;

        /// <summary>
        /// The default vertical alignment for <see cref="Brush"/> fills.
        /// This is the default value for the <see cref="Fill.AlignV"/> property.
        /// </summary>
        public static AlignV AlignV = AlignV.Center;
    }

    #endregion

    #region Constructors

    /// <summary>
    /// Generic initializer to default values
    /// </summary>
    private void Init()
    {
        _color = Color.White;
        _secondaryValueGradientColor = Color.White;
        _brush = null;
        _type = FillType.None;
        _isScaled = Default.IsScaled;
        _alignH = Default.AlignH;
        _alignV = Default.AlignV;
        _rangeMin = 0.0;
        _rangeMax = 1.0;
        _rangeDefault = double.MaxValue;
        _gradientBM = null!;

        _colorList = null;
        _positionList = null!;
        _angle = 0;
        _image = null!;
        _wrapMode = WrapMode.Tile;
    }

    /// <summary>
    /// The default constructor.  Initialized to no fill.
    /// </summary>
    public Fill()
    {
        _gradientBM = null!;
        _image = null!;
        _positionList = null!;

        Init();
    }

    /// <summary>
    /// Constructor that specifies the color, brush, and type for this fill.
    /// </summary>
    /// <param name="color">The color of the fill for solid fills</param>
    /// <param name="brush">A custom brush for fills.  Can be a <see cref="SolidBrush"/>,
    /// <see cref="LinearGradientBrush"/>, or <see cref="TextureBrush"/>.</param>
    /// <param name="type">The <see cref="FillType"/> for this fill.</param>
    public Fill
        (
            Color color,
            Brush? brush,
            FillType type
        )
    {
        _gradientBM = null!;
        _image = null!;
        _positionList = null!;

        Init();
        _color = color;
        _brush = brush;
        _type = type;
    }

    /// <summary>
    /// Constructor that creates a solid color-fill, setting <see cref="Type"/> to
    /// <see cref="FillType.Solid"/>, and setting <see cref="Color"/> to the
    /// specified color value.
    /// </summary>
    /// <param name="color">The color of the solid fill</param>
    public Fill (Color color)
    {
        _gradientBM = null!;
        _image = null!;
        _positionList = null!;

        Init();
        _color = color;
        if (color != Color.Empty)
        {
            _type = FillType.Solid;
        }
    }

    /// <summary>
    /// Constructor that creates a linear gradient color-fill, setting <see cref="Type"/> to
    /// <see cref="FillType.Brush"/> using the specified colors and angle.
    /// </summary>
    /// <param name="color1">The first color for the gradient fill</param>
    /// <param name="color2">The second color for the gradient fill</param>
    /// <param name="angle">The angle (degrees) of the gradient fill</param>
    public Fill (Color color1, Color color2, float angle)
    {
        _gradientBM = null!;
        _image = null!;
        _positionList = null!;

        Init();
        _color = color2;

        var blend = new ColorBlend (2)
        {
            Colors =
            {
                [0] = color1,
                [1] = color2
            },
            Positions =
            {
                [0] = 0.0f,
                [1] = 1.0f
            }
        };
        _type = FillType.Brush;

        CreateBrushFromBlend (blend, angle);
    }

    /// <summary>
    /// Constructor that creates a linear gradient color-fill, setting <see cref="Type"/> to
    /// <see cref="FillType.Brush"/> using the specified colors.
    /// </summary>
    /// <param name="color1">The first color for the gradient fill</param>
    /// <param name="color2">The second color for the gradient fill</param>
    public Fill (Color color1, Color color2) : this (color1, color2, 0.0F)
    {
        // пустое тело конструктора
    }

    /// <summary>
    /// Constructor that creates a linear gradient color-fill, setting <see cref="Type"/> to
    /// <see cref="FillType.Brush"/> using the specified colors.  This gradient fill
    /// consists of three colors.
    /// </summary>
    /// <param name="color1">The first color for the gradient fill</param>
    /// <param name="color2">The second color for the gradient fill</param>
    /// <param name="color3">The third color for the gradient fill</param>
    public Fill (Color color1, Color color2, Color color3) :
        this (color1, color2, color3, 0.0f)
    {
        // пустое тело конструктора
    }

    /// <summary>
    /// Constructor that creates a linear gradient color-fill, setting <see cref="Type"/> to
    /// <see cref="FillType.Brush"/> using the specified colors.  This gradient fill
    /// consists of three colors
    /// </summary>
    /// <param name="color1">The first color for the gradient fill</param>
    /// <param name="color2">The second color for the gradient fill</param>
    /// <param name="color3">The third color for the gradient fill</param>
    /// <param name="angle">The angle (degrees) of the gradient fill</param>
    public Fill (Color color1, Color color2, Color color3, float angle)
    {
        _gradientBM = null!;
        _image = null!;
        _positionList = null!;

        Init();
        _color = color3;

        var blend = new ColorBlend (3)
        {
            Colors =
            {
                [0] = color1,
                [1] = color2,
                [2] = color3
            },
            Positions =
            {
                [0] = 0.0f,
                [1] = 0.5f,
                [2] = 1.0f
            }
        };
        _type = FillType.Brush;

        CreateBrushFromBlend (blend, angle);
    }

    /// <summary>
    /// Constructor that creates a linear gradient multi-color-fill, setting <see cref="Type"/> to
    /// <see cref="FillType.Brush"/> using the specified colors.  This gradient fill
    /// consists of many colors based on a <see cref="ColorBlend"/> object.  The gradient
    /// angle is defaulted to zero.
    /// </summary>
    /// <param name="blend">The <see cref="ColorBlend"/> object that defines the colors
    /// and positions along the gradient.</param>
    public Fill (ColorBlend blend) :
        this (blend, 0.0F)
    {
        // пустое тело конструктора
    }

    /// <summary>
    /// Constructor that creates a linear gradient multi-color-fill, setting <see cref="Type"/> to
    /// <see cref="FillType.Brush"/> using the specified colors.  This gradient fill
    /// consists of many colors based on a <see cref="ColorBlend"/> object, drawn at the
    /// specified angle (degrees).
    /// </summary>
    /// <param name="blend">The <see cref="ColorBlend"/> object that defines the colors
    /// and positions along the gradient.</param>
    /// <param name="angle">The angle (degrees) of the gradient fill</param>
    public Fill (ColorBlend blend, float angle)
    {
        _gradientBM = null!;
        _image = null!;
        _positionList = null!;

        Init();
        _type = FillType.Brush;
        CreateBrushFromBlend (blend, angle);
    }

    /// <summary>
    /// Constructor that creates a linear gradient multi-color-fill, setting <see cref="Type"/> to
    /// <see cref="FillType.Brush"/> using the specified colors.  This gradient fill
    /// consists of many colors based on an array of <see cref="Color"/> objects, drawn at an
    /// angle of zero (degrees).  The <see paramref="colors"/> array is used to create
    /// a <see cref="ColorBlend"/> object assuming a even linear distribution of the colors
    /// across the gradient.
    /// </summary>
    /// <param name="colors">The array of <see cref="Color"/> objects that defines the colors
    /// along the gradient.</param>
    public Fill (Color[] colors) :
        this (colors, 0.0F)
    {
        // пустое тело конструктора
    }

    /// <summary>
    /// Constructor that creates a linear gradient multi-color-fill, setting <see cref="Type"/> to
    /// <see cref="FillType.Brush"/> using the specified colors.  This gradient fill
    /// consists of many colors based on an array of <see cref="Color"/> objects, drawn at the
    /// specified angle (degrees).  The <see paramref="colors"/> array is used to create
    /// a <see cref="ColorBlend"/> object assuming a even linear distribution of the colors
    /// across the gradient.
    /// </summary>
    /// <param name="colors">The array of <see cref="Color"/> objects that defines the colors
    /// along the gradient.</param>
    /// <param name="angle">The angle (degrees) of the gradient fill</param>
    public Fill (Color[] colors, float angle)
    {
        _gradientBM = null!;
        _image = null!;
        _positionList = null!;

        Init();
        _color = colors[^1];

        var blend = new ColorBlend
        {
            Colors = colors,
            Positions = new float[colors.Length]
        };
        blend.Positions[0] = 0.0F;
        for (var i = 1; i < colors.Length; i++)
        {
            blend.Positions[i] = (float)i / (float)(colors.Length - 1);
        }

        _type = FillType.Brush;

        CreateBrushFromBlend (blend, angle);
    }

    /// <summary>
    /// Constructor that creates a linear gradient multi-color-fill, setting <see cref="Type"/> to
    /// <see cref="FillType.Brush"/> using the specified colors.  This gradient fill
    /// consists of many colors based on an array of <see cref="Color"/> objects, drawn at the
    /// an angle of zero (degrees).  The <see paramref="colors"/> array is used to create
    /// a <see cref="ColorBlend"/> object assuming a even linear distribution of the colors
    /// across the gradient.
    /// </summary>
    /// <param name="colors">The array of <see cref="Color"/> objects that defines the colors
    /// along the gradient.</param>
    /// <param name="positions">The array of floating point values that defines the color
    /// positions along the gradient.  Values should range from 0 to 1.</param>
    public Fill (Color[] colors, float[] positions) :
        this (colors, positions, 0.0F)
    {
        // пустое тело конструктора
    }

    /// <summary>
    /// Constructor that creates a linear gradient multi-color-fill, setting <see cref="Type"/> to
    /// <see cref="FillType.Brush"/> using the specified colors.  This gradient fill
    /// consists of many colors based on an array of <see cref="Color"/> objects, drawn at the
    /// specified angle (degrees).  The <see paramref="colors"/> array is used to create
    /// a <see cref="ColorBlend"/> object assuming a even linear distribution of the colors
    /// across the gradient.
    /// </summary>
    /// <param name="colors">The array of <see cref="Color"/> objects that defines the colors
    /// along the gradient.</param>
    /// <param name="positions">The array of floating point values that defines the color
    /// positions along the gradient.  Values should range from 0 to 1.</param>
    /// <param name="angle">The angle (degrees) of the gradient fill</param>
    public Fill (Color[] colors, float[] positions, float angle)
    {
        _gradientBM = null!;
        _image = null!;
        _positionList = null!;

        Init();
        _color = colors[^1];

        var blend = new ColorBlend
        {
            Colors = colors,
            Positions = positions
        };
        _type = FillType.Brush;

        CreateBrushFromBlend (blend, angle);
    }

    /// <summary>
    /// Constructor that creates a texture fill, setting <see cref="Type"/> to
    /// <see cref="FillType.Brush"/> and using the specified image.
    /// </summary>
    /// <param name="image">The <see cref="Image"/> to use for filling</param>
    /// <param name="wrapMode">The <see cref="WrapMode"/> class that controls the image wrapping properties</param>
    public Fill (Image image, WrapMode wrapMode)
    {
        _gradientBM = null!;
        _image = null!;
        _positionList = null!;

        Init();
        _color = Color.White;
        _brush = new TextureBrush (image, wrapMode);
        _type = FillType.Brush;
        _image = image;
        _wrapMode = wrapMode;
    }

    /// <summary>
    /// Constructor that creates a <see cref="Brush"/> fill, using a user-supplied, custom
    /// <see cref="Brush"/>.  The brush will be scaled to fit the destination screen object
    /// unless you manually change <see cref="IsScaled"/> to false;
    /// </summary>
    /// <param name="brush">The <see cref="Brush"/> to use for fancy fills.  Typically, this would
    /// be a <see cref="LinearGradientBrush"/> or a <see cref="TextureBrush"/> class</param>
    public Fill (Brush brush) : this (brush, Default.IsScaled)
    {
        // пустое тело конструктора
    }

    /// <summary>
    /// Constructor that creates a <see cref="Brush"/> fill, using a user-supplied, custom
    /// <see cref="Brush"/>.  The brush will be scaled to fit the destination screen object
    /// according to the <see paramref="isScaled"/> parameter.
    /// </summary>
    /// <param name="brush">The <see cref="Brush"/> to use for fancy fills.  Typically, this would
    /// be a <see cref="LinearGradientBrush"/> or a <see cref="TextureBrush"/> class</param>
    /// <param name="isScaled">Determines if the brush will be scaled to fit the bounding box
    /// of the destination object.  true to scale it, false to leave it unscaled</param>
    public Fill (Brush brush, bool isScaled)
    {
        _gradientBM = null!;
        _image = null!;
        _positionList = null!;

        Init();
        _isScaled = isScaled;
        _color = Color.White;
        _brush = (Brush)brush.Clone();
        _type = FillType.Brush;
    }

    /// <summary>
    /// Constructor that creates a <see cref="Brush"/> fill, using a user-supplied, custom
    /// <see cref="Brush"/>.  This constructor will make the brush unscaled (see <see cref="IsScaled"/>),
    /// but it provides <see paramref="alignH"/> and <see paramref="alignV"/> parameters to control
    /// alignment of the brush with respect to the filled object.
    /// </summary>
    /// <param name="brush">The <see cref="Brush"/> to use for fancy fills.  Typically, this would
    /// be a <see cref="LinearGradientBrush"/> or a <see cref="TextureBrush"/> class</param>
    /// <param name="alignH">Controls the horizontal alignment of the brush within the filled object
    /// (see <see cref="AlignH"/></param>
    /// <param name="alignV">Controls the vertical alignment of the brush within the filled object
    /// (see <see cref="AlignV"/></param>
    public Fill (Brush brush, AlignH alignH, AlignV alignV)
    {
        _gradientBM = null!;
        _image = null!;
        _positionList = null!;

        Init();
        _alignH = alignH;
        _alignV = alignV;
        _isScaled = false;
        _color = Color.White;
        _brush = (Brush)brush.Clone();
        _type = FillType.Brush;
    }

    /// <summary>
    /// The Copy Constructor
    /// </summary>
    /// <param name="rhs">The Fill object from which to copy</param>
    public Fill (Fill rhs)
    {
        _gradientBM = null!;
        _image = null!;
        _positionList = null!;

        _color = rhs._color;
        _secondaryValueGradientColor = rhs._color;

        if (rhs._brush != null)
        {
            _brush = (Brush)rhs._brush.Clone();
        }
        else
        {
            _brush = null;
        }

        _type = rhs._type;
        _alignH = rhs.AlignH;
        _alignV = rhs.AlignV;
        _isScaled = rhs.IsScaled;
        _rangeMin = rhs._rangeMin;
        _rangeMax = rhs._rangeMax;
        _rangeDefault = rhs._rangeDefault;

        if (rhs._colorList != null)
        {
            _colorList = (Color[])rhs._colorList.Clone();
        }
        else
        {
            _colorList = null;
        }

        if (rhs._positionList != null!)
        {
            _positionList = (float[])rhs._positionList.Clone();
        }
        else
        {
            _positionList = null!;
        }

        if (rhs._image != null!)
        {
            _image = (Image)rhs._image.Clone();
        }
        else
        {
            _image = null!;
        }

        _angle = rhs._angle;
        _wrapMode = rhs._wrapMode;
    }

    /// <summary>
    /// Implement the <see cref="ICloneable" /> interface in a typesafe manner by just
    /// calling the typed version of <see cref="Clone" />
    /// </summary>
    /// <returns>A deep copy of this object</returns>
    object ICloneable.Clone()
    {
        return Clone();
    }

    /// <summary>
    /// Typesafe, deep-copy clone method.
    /// </summary>
    /// <returns>A new, independent copy of this class</returns>
    public Fill Clone()
    {
        return new Fill (this);
    }

    private void CreateBrushFromBlend (ColorBlend blend, float angle)
    {
        _angle = angle;

        _colorList = (Color[])blend.Colors.Clone();
        _positionList = (float[])blend.Positions.Clone();

        _brush = new LinearGradientBrush (new Rectangle (0, 0, 100, 100),
            Color.Red, Color.White, angle);
        ((LinearGradientBrush)_brush).InterpolationColors = blend;
    }

    #endregion

    #region Serialization

    /// <summary>
    /// Current schema value that defines the version of the serialized file
    /// </summary>
    public const int schema = 10;

    // schema changed to 2 with addition of rangeDefault
    // schema changed to 10 with version 5 refactor -- not backwards compatible

    /// <summary>
    /// Constructor for deserializing objects
    /// </summary>
    /// <param name="info">A <see cref="SerializationInfo"/> instance that defines the serialized data
    /// </param>
    /// <param name="context">A <see cref="StreamingContext"/> instance that contains the serialized data
    /// </param>
    protected Fill
        (
            SerializationInfo info,
            StreamingContext context
        )
    {
        _gradientBM = null!;
        _image = null!;
        _positionList = null!;

        Init();

        // The schema value is just a file version parameter.  You can use it to make future versions
        // backwards compatible as new member variables are added to classes
        info.GetInt32 ("schema").NotUsed();

        _color = (Color)info.GetValue ("color", typeof (Color))!;
        _secondaryValueGradientColor = (Color)info.GetValue ("secondaryValueGradientColor", typeof (Color))!;

        //brush = (Brush) info.GetValue( "brush", typeof(Brush) );
        //brushHolder = (BrushHolder) info.GetValue( "brushHolder", typeof(BrushHolder) );
        _type = (FillType)info.GetValue ("type", typeof (FillType))!;
        _isScaled = info.GetBoolean ("isScaled");
        _alignH = (AlignH)info.GetValue ("alignH", typeof (AlignH))!;
        _alignV = (AlignV)info.GetValue ("alignV", typeof (AlignV))!;
        _rangeMin = info.GetDouble ("rangeMin");
        _rangeMax = info.GetDouble ("rangeMax");

        //BrushHolder brushHolder = (BrushHolder) info.GetValue( "brushHolder", typeof( BrushHolder ) );
        //brush = brush;

        _colorList = (Color[])info.GetValue ("colorList", typeof (Color[]))!;
        _positionList = (float[])info.GetValue ("positionList", typeof (float[]))!;
        _angle = info.GetSingle ("angle");
        _image = (Image)info.GetValue ("image", typeof (Image))!;
        _wrapMode = (WrapMode)info.GetValue ("wrapMode", typeof (WrapMode))!;

        if (_colorList != null && _positionList != null!)
        {
            var blend = new ColorBlend
            {
                Colors = _colorList,
                Positions = _positionList
            };
            CreateBrushFromBlend (blend, _angle);
        }
        else if (_image != null!)
        {
            _brush = new TextureBrush (_image, _wrapMode);
        }

        _rangeDefault = info.GetDouble ("rangeDefault");
    }

    /// <inheritdoc cref="ISerializable.GetObjectData"/>
    public virtual void GetObjectData
        (
            SerializationInfo info,
            StreamingContext context
        )
    {
        info.AddValue ("schema", schema);
        info.AddValue ("color", _color);
        info.AddValue ("secondaryValueGradientColor", _secondaryValueGradientColor);

        //info.AddValue( "brush", brush );
        //info.AddValue( "brushHolder", brushHolder );
        info.AddValue ("type", _type);
        info.AddValue ("isScaled", _isScaled);
        info.AddValue ("alignH", _alignH);
        info.AddValue ("alignV", _alignV);
        info.AddValue ("rangeMin", _rangeMin);
        info.AddValue ("rangeMax", _rangeMax);

        //BrushHolder brushHolder = new BrushHolder();
        //brush = brush;
        //info.AddValue( "brushHolder", brushHolder );

        info.AddValue ("colorList", _colorList);
        info.AddValue ("positionList", _positionList);
        info.AddValue ("angle", _angle);
        info.AddValue ("image", _image);
        info.AddValue ("wrapMode", _wrapMode);

        info.AddValue ("rangeDefault", _rangeDefault);
    }

    #endregion

    #region Properties

    /// <summary>
    /// The fill color.  This property is used as a single color to make a solid fill
    /// (<see cref="Type"/> is <see cref="FillType.Solid"/>), or it can be used in
    /// combination with <see cref="System.Drawing.Color.White"/> to make a
    /// <see cref="LinearGradientBrush"/>
    /// when <see cref="Type"/> is <see cref="FillType.Brush"/> and <see cref="Brush"/>
    /// is null.
    /// </summary>
    /// <seealso cref="Type"/>
    public Color Color
    {
        get => _color;
        set => _color = value;
    }

    /// <summary>
    /// Gets or sets the secondary color for gradientByValue fills.
    /// </summary>
    /// <remarks>
    /// This property is only applicable if the <see cref="Type"/> is
    /// <see cref="FillType.GradientByX"/>,
    /// <see cref="FillType.GradientByY"/>, or
    /// <see cref="FillType.GradientByZ"/>.  Once the gradient-by-value logic picks
    /// a color, a new gradient will be created using the SecondaryValueGradientColor, the
    /// resulting gradient-by-value color, and the angle setting for this
    /// <see cref="Fill" />. Use a value of <see cref="System.Drawing.Color.Empty">Color.Empty</see> to have
    /// a solid-color <see cref="Fill" /> resulting from a gradient-by-value
    /// <see cref="FillType" />.
    /// </remarks>
    public Color SecondaryValueGradientColor
    {
        get => _secondaryValueGradientColor;
        set => _secondaryValueGradientColor = value;
    }

    /// <summary>
    /// The custom fill brush.  This can be a <see cref="SolidBrush"/>, a
    /// <see cref="LinearGradientBrush"/>, or a <see cref="TextureBrush"/>.  This property is
    /// only applicable if the <see cref="Type"/> property is set
    /// to <see cref="FillType.Brush"/>.
    /// </summary>
    public Brush Brush
    {
        get => _brush!;
        set => _brush = value;
    }

    /// <summary>
    /// Determines the type of fill, which can be either solid
    /// color (<see cref="FillType.Solid"/>) or a custom brush
    /// (<see cref="FillType.Brush"/>).  See <see cref="Type"/> for
    /// more information.
    /// </summary>
    /// <seealso cref="Fill.Color"/>
    public FillType Type
    {
        get => _type;
        set => _type = value;
    }

    /// <summary>
    /// This property determines the type of color fill.
    /// Returns true if the <see cref="Type"/> property is either
    /// <see cref="FillType.Solid"/> or
    /// <see cref="FillType.Brush"/>.  If set to true, this property
    /// will automatically set the <see cref="Type"/> to
    /// <see cref="FillType.Brush"/>.  If set to false, this property
    /// will automatically set the <see cref="Type"/> to
    /// <see cref="FillType.None"/>.  In order to get a regular
    /// solid-color fill, you have to manually set <see cref="Type"/>
    /// to <see cref="FillType.Solid"/>.
    /// </summary>
    /// <seealso cref="Color"/>
    /// <seealso cref="Brush"/>
    /// <seealso cref="Type"/>
    public bool IsVisible
    {
        get => _type != FillType.None;
        set => _type = value ? (_type == FillType.None ? FillType.Brush : _type) : FillType.None;
    }

    /// <summary>
    /// Determines if the brush will be scaled to the bounding box
    /// of the filled object.  If this value is false, then the brush will only be aligned
    /// with the filled object based on the <see cref="AlignH"/> and <see cref="AlignV"/>
    /// properties.
    /// </summary>
    public bool IsScaled
    {
        get => _isScaled;
        set => _isScaled = value;
    }

    /// <summary>
    /// Determines how the brush will be aligned with the filled object
    /// in the horizontal direction.  This value is a <see cref="Charting.AlignH"/> enumeration.
    /// This field only applies if <see cref="IsScaled"/> is false.
    /// </summary>
    /// <seealso cref="AlignV"/>
    public AlignH AlignH
    {
        get => _alignH;
        set => _alignH = value;
    }

    /// <summary>
    /// Determines how the brush will be aligned with the filled object
    /// in the vertical direction.  This value is a <see cref="Charting.AlignV"/> enumeration.
    /// This field only applies if <see cref="IsScaled"/> is false.
    /// </summary>
    /// <seealso cref="AlignH"/>
    public AlignV AlignV
    {
        get => _alignV;
        set => _alignV = value;
    }

    /// <summary>
    /// Returns a boolean value indicating whether or not this fill is a "Gradient-By-Value"
    /// type.  This is true for <see cref="FillType.GradientByX"/>, <see cref="FillType.GradientByY"/>,
    /// or <see cref="FillType.GradientByZ"/>.
    /// </summary>
    /// <remarks>
    /// The gradient by value fill method allows the fill color for each point or bar to
    /// be based on a value for that point (either X, Y, or Z in the <see cref="IPointList"/>.
    /// For example, assume a <see cref="Fill"/> class is defined with a linear gradient ranging from
    /// <see cref="System.Drawing.Color.Blue"/> to <see cref="System.Drawing.Color.Red"/> and the <see cref="Fill.Type"/>
    /// is set to <see cref="FillType.GradientByY"/>.  If <see cref="RangeMin"/> is set to
    /// 100.0 and <see cref="RangeMax"/> is set to 200.0, then a point that has a Y value of
    /// 100 or less will be colored blue, a point with a Y value of 200 or more will be
    /// colored red, and a point between 100 and 200 will have a color based on a linear scale
    /// between blue and red.  Note that the fill color is always solid for any given point.
    /// You can use the Z value from <see cref="IPointList"/> along with
    /// <see cref="FillType.GradientByZ"/> to color individual points according to some
    /// property that is independent of the X,Y point pair.
    /// </remarks>
    /// <value>true if this is a Gradient-by-value type, false otherwise</value>
    /// <seealso cref="FillType.GradientByX"/>
    /// <seealso cref="FillType.GradientByY"/>
    /// <seealso cref="FillType.GradientByZ"/>
    public bool IsGradientValueType =>
        _type == FillType.GradientByX || _type == FillType.GradientByY ||
        _type == FillType.GradientByZ || _type == FillType.GradientByColorValue;

    /// <summary>
    /// The minimum user-scale value for the gradient-by-value determination.  This defines
    /// the user-scale value for the start of the gradient.
    /// </summary>
    /// <seealso cref="FillType.GradientByX"/>
    /// <seealso cref="FillType.GradientByY"/>
    /// <seealso cref="FillType.GradientByZ"/>
    /// <seealso cref="IsGradientValueType"/>
    /// <seealso cref="RangeMax"/>
    /// <seealso cref="RangeDefault"/>
    /// <value>A double value, in user scale unit</value>
    public double RangeMin
    {
        get => _rangeMin;
        set => _rangeMin = value;
    }

    /// <summary>
    /// The maximum user-scale value for the gradient-by-value determination.  This defines
    /// the user-scale value for the end of the gradient.
    /// </summary>
    /// <seealso cref="FillType.GradientByX"/>
    /// <seealso cref="FillType.GradientByY"/>
    /// <seealso cref="FillType.GradientByZ"/>
    /// <seealso cref="IsGradientValueType"/>
    /// <seealso cref="RangeMin"/>
    /// <seealso cref="RangeDefault"/>
    /// <value>A double value, in user scale unit</value>
    public double RangeMax
    {
        get => _rangeMax;
        set => _rangeMax = value;
    }

    /// <summary>
    /// The default user-scale value for the gradient-by-value determination.  This defines the
    /// value that will be used when there is no point value available, or the actual point value
    /// is invalid.
    /// </summary>
    /// <remarks>
    /// Note that this value, when defined, will determine the color that is used in the legend.
    /// If this value is set to double.MaxValue, then it remains "undefined."  In this case, the
    /// legend symbols will actually be filled with a color gradient representing the range of
    /// colors.
    /// </remarks>
    /// <seealso cref="FillType.GradientByX"/>
    /// <seealso cref="FillType.GradientByY"/>
    /// <seealso cref="FillType.GradientByZ"/>
    /// <seealso cref="IsGradientValueType"/>
    /// <seealso cref="RangeMin"/>
    /// <seealso cref="RangeMax"/>
    /// <value>A double value, in user scale unit</value>
    public double RangeDefault
    {
        get => _rangeDefault;
        set => _rangeDefault = value;
    }

    #endregion

    #region Methods

    /// <summary>
    /// Create a fill brush using current properties.  This method will construct a brush based on the
    /// settings of <see cref="Fill.Type"/>, <see cref="Fill.Color"/>
    /// and <see cref="Fill.Brush"/>.  If
    /// <see cref="Fill.Type"/> is set to <see cref="FillType.Brush"/> and
    /// <see cref="Fill.Brush"/>
    /// is null, then a <see cref="LinearGradientBrush"/> will be created between the colors of
    /// <see cref="System.Drawing.Color.White"/> and <see cref="Fill.Color"/>.
    /// </summary>
    /// <param name="rect">A rectangle that bounds the object to be filled.  This determines
    /// the start and end of the gradient fill.</param>
    /// <returns>A <see cref="System.Drawing.Brush"/> class representing the fill brush</returns>
    public Brush MakeBrush (RectangleF rect)
    {
        // just provide a default value for the valueFraction
        // return MakeBrush( rect, new PointPair( 0.5, 0.5, 0.5 ) );
        return MakeBrush (rect, null);
    }

    /// <summary>
    /// Create a fill brush using current properties.  This method will construct a brush based on the
    /// settings of <see cref="Fill.Type"/>, <see cref="Fill.Color"/>
    /// and <see cref="Fill.Brush"/>.  If
    /// <see cref="Fill.Type"/> is set to <see cref="FillType.Brush"/> and
    /// <see cref="Fill.Brush"/>
    /// is null, then a <see cref="LinearGradientBrush"/> will be created between the colors of
    /// <see cref="System.Drawing.Color.White"/> and <see cref="Fill.Color"/>.
    /// </summary>
    /// <param name="rect">A rectangle that bounds the object to be filled.  This determines
    /// the start and end of the gradient fill.</param>
    /// <param name="dataValue">The data value to be used for a value-based
    /// color gradient.  This is only applicable for <see cref="FillType.GradientByX"/>,
    /// <see cref="FillType.GradientByY"/> or <see cref="FillType.GradientByZ"/>.</param>
    /// <returns>A <see cref="System.Drawing.Brush"/> class representing the fill brush</returns>
    public Brush MakeBrush
        (
            RectangleF rect,
            PointPair? dataValue
        )
    {
        // get a brush
        if (IsVisible && (!_color.IsEmpty || _brush != null))
        {
            if (rect.Height < 1.0F)
            {
                rect.Height = 1.0F;
            }

            if (rect.Width < 1.0F)
            {
                rect.Width = 1.0F;
            }

            //Brush	brush;
            if (_type == FillType.Brush)
            {
                return ScaleBrush (rect, _brush!, _isScaled);
            }
            else if (IsGradientValueType)
            {
                if (dataValue != null)
                {
                    if (!_secondaryValueGradientColor.IsEmpty)
                    {
                        // Go ahead and create a new Fill so we can do all the scaling, etc.,
                        // that is associated with a gradient
                        var tmpFill = new Fill (_secondaryValueGradientColor,
                            GetGradientColor (dataValue), _angle);
                        return tmpFill.MakeBrush (rect);
                    }
                    else
                    {
                        return new SolidBrush (GetGradientColor (dataValue));
                    }
                }
                else if (_rangeDefault != double.MaxValue)
                {
                    if (!_secondaryValueGradientColor.IsEmpty)
                    {
                        // Go ahead and create a new Fill so we can do all the scaling, etc.,
                        // that is associated with a gradient
                        var tmpFill = new Fill (_secondaryValueGradientColor,
                            GetGradientColor (_rangeDefault), _angle);
                        return tmpFill.MakeBrush (rect);
                    }
                    else
                    {
                        return new SolidBrush (GetGradientColor (_rangeDefault));
                    }
                }
                else
                {
                    return ScaleBrush (rect, _brush!, true);
                }
            }
            else
            {
                return new SolidBrush (_color);
            }
        }

        // Always return a suitable default
        return new SolidBrush (Color.White);
    }

    internal Color GetGradientColor (PointPair dataValue)
    {
        double val;

        if (dataValue == null!)
        {
            val = _rangeDefault;
        }
        else if (_type == FillType.GradientByColorValue)
        {
            val = dataValue.ColorValue;
        }
        else if (_type == FillType.GradientByZ)
        {
            val = dataValue.Z;
        }
        else if (_type == FillType.GradientByY)
        {
            val = dataValue.Y;
        }
        else
        {
            val = dataValue.X;
        }

        return GetGradientColor (val);
    }

    internal Color GetGradientColor (double val)
    {
        double valueFraction;

        if (double.IsInfinity (val) || double.IsNaN (val) || val == PointPairBase.Missing)
        {
            val = _rangeDefault;
        }

        if (_rangeMax - _rangeMin < 1e-20 || val == double.MaxValue)
        {
            valueFraction = 0.5;
        }
        else
        {
            valueFraction = (val - _rangeMin) / (_rangeMax - _rangeMin);
        }

        if (valueFraction < 0.0)
        {
            valueFraction = 0.0;
        }
        else if (valueFraction > 1.0)
        {
            valueFraction = 1.0;
        }

        if (_gradientBM == null!)
        {
            var rect = new RectangleF (0, 0, 100, 1);
            _gradientBM = new Bitmap (100, 1);
            var gBM = Graphics.FromImage (_gradientBM);

            var tmpBrush = ScaleBrush (rect, _brush!, true);
            gBM.FillRectangle (tmpBrush, rect);
        }

        return _gradientBM.GetPixel ((int)(99.9 * valueFraction), 0);
    }

    private Brush ScaleBrush (RectangleF rect, Brush brush, bool isScaled)
    {
        if (brush != null!)
        {
            if (brush is SolidBrush)
            {
                return (Brush)brush.Clone();
            }
            else if (brush is LinearGradientBrush)
            {
                var linBrush = (LinearGradientBrush)brush.Clone();

                if (isScaled)
                {
                    linBrush.ScaleTransform (rect.Width / linBrush.Rectangle.Width,
                        rect.Height / linBrush.Rectangle.Height, MatrixOrder.Append);
                    linBrush.TranslateTransform (rect.Left - linBrush.Rectangle.Left,
                        rect.Top - linBrush.Rectangle.Top, MatrixOrder.Append);
                }
                else
                {
                    float dx = 0,
                        dy = 0;
                    switch (_alignH)
                    {
                        case AlignH.Left:
                            dx = rect.Left - linBrush.Rectangle.Left;
                            break;
                        case AlignH.Center:
                            dx = (rect.Left + rect.Width / 2.0F) - linBrush.Rectangle.Left;
                            break;
                        case AlignH.Right:
                            dx = (rect.Left + rect.Width) - linBrush.Rectangle.Left;
                            break;
                    }

                    switch (_alignV)
                    {
                        case AlignV.Top:
                            dy = rect.Top - linBrush.Rectangle.Top;
                            break;
                        case AlignV.Center:
                            dy = (rect.Top + rect.Height / 2.0F) - linBrush.Rectangle.Top;
                            break;
                        case AlignV.Bottom:
                            dy = (rect.Top + rect.Height) - linBrush.Rectangle.Top;
                            break;
                    }

                    linBrush.TranslateTransform (dx, dy, MatrixOrder.Append);
                }

                return linBrush;
            } // LinearGradientBrush
            else if (brush is TextureBrush)
            {
                var texBrush = (TextureBrush)brush.Clone();

                if (isScaled)
                {
                    texBrush.ScaleTransform (rect.Width / texBrush.Image.Width,
                        rect.Height / texBrush.Image.Height, MatrixOrder.Append);
                    texBrush.TranslateTransform (rect.Left, rect.Top, MatrixOrder.Append);
                }
                else
                {
                    float dx = 0,
                        dy = 0;
                    switch (_alignH)
                    {
                        case AlignH.Left:
                            dx = rect.Left;
                            break;

                        case AlignH.Center:
                            dx = (rect.Left + rect.Width / 2.0F);
                            break;

                        case AlignH.Right:
                            dx = (rect.Left + rect.Width);
                            break;
                    }

                    switch (_alignV)
                    {
                        case AlignV.Top:
                            dy = rect.Top;
                            break;

                        case AlignV.Center:
                            dy = (rect.Top + rect.Height / 2.0F);
                            break;

                        case AlignV.Bottom:
                            dy = (rect.Top + rect.Height);
                            break;
                    }

                    texBrush.TranslateTransform (dx, dy, MatrixOrder.Append);
                }

                return texBrush;
            }
            else // other brush type
            {
                return (Brush)brush.Clone();
            }
        }
        else

            // If they didn't provide a brush, make one using the fillcolor gradient to white
        {
            return new LinearGradientBrush (rect, Color.White, _color, 0F);
        }
    }

    /// <summary>
    /// Fill the background of the <see cref="RectangleF"/> area, using the
    /// fill type from this <see cref="Fill"/>.
    /// </summary>
    /// <param name="graphics">
    /// A graphic device object to be drawn into.  This is normally e.Graphics from the
    /// PaintEventArgs argument to the Paint() method.
    /// </param>
    /// <param name="rect">The <see cref="RectangleF"/> struct specifying the area
    /// to be filled</param>
    public void Draw
        (
            Graphics graphics,
            RectangleF rect
        )
    {
        Draw (graphics, rect, null);
        /*
        if ( this.IsVisible )
        {
            using( Brush brush = this.MakeBrush( rect ) )
            {
                g.FillRectangle( brush, rect );
                //brush.Dispose();
            }
        }
        */
    }

    /// <summary>
    /// Fill the background of the <see cref="RectangleF"/> area, using the
    /// fill type from this <see cref="Fill"/>.
    /// </summary>
    /// <param name="graphics">
    /// A graphic device object to be drawn into.  This is normally e.Graphics from the
    /// PaintEventArgs argument to the Paint() method.
    /// </param>
    /// <param name="rect">The <see cref="RectangleF"/> struct specifying the area
    /// to be filled</param>
    /// <param name="pt">The data value to be used in case it's a
    /// <see cref="FillType.GradientByX" />, <see cref="FillType.GradientByY" />, or
    /// <see cref="FillType.GradientByZ" /> <see cref="FillType" />.</param>
    public void Draw
        (
            Graphics graphics,
            RectangleF rect,
            PointPair? pt
        )
    {
        if (IsVisible)
        {
            using var brush = MakeBrush (rect, pt);
            graphics.FillRectangle (brush, rect);
        }
    }

    #endregion
}
